/*
* Copyright (c) 2002 - 2011
* NetGroup, Politecnico di Torino (Italy)
* All rights reserved.
* 
* Redistribution and use in source and binary forms, with or without 
* modification, are permitted provided that the following condition 
* is met:
* 
* Neither the name of the Politecnico di Torino nor the names of its 
* contributors may be used to endorse or promote products derived from 
* this software without specific prior written permission. 
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* 
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <nbnetvm.h>

#ifdef WIN32
#define snprintf _snprintf
#endif

#define DEFAULT_NETILFILENAME "../samples/nbnetvm/netilsamples/ip.netil"
//#define DEFAULT_NETILFILENAME "../netilsamples/ip.netil"
#define DEFAULT_NATIVEASMFILENAME "netvmcompiler"

struct options
{
	char* OutputFilePrefix;
	uint32_t OptLevel;
	uint32_t BackendID;
	uint32_t DoInline;
	uint32_t DoBcheck;
};


int NetVMSetup(char** FileNamesArray, uint32_t NumNetILFiles, struct options* NetVMOptions) 
{
char		Errbuf[nvmERRBUF_SIZE];
nvmByteCode * BytecodeHandle= NULL;
nvmNetVM	*NetVMHandler= NULL;
nvmNetPE	*CurrentNetPE= NULL;
nvmNetPE	*PrevNetPE= NULL;
nvmSocket	*SocketIn= NULL;
nvmSocket	*SocketOut= NULL;
nvmRuntimeEnvironment *NetVMRuntimeHandler= NULL;
nvmAppInterface *InInterf= NULL;
nvmAppInterface *OutInterf= NULL;
uint32_t JitFlags = 0;
unsigned int i;
	

	// Create the NetVM application
	NetVMHandler = nvmCreateVM(1, Errbuf);

	// Create the netvm sockets
	SocketIn = nvmCreateSocket(NetVMHandler, Errbuf);
	SocketOut = nvmCreateSocket(NetVMHandler, Errbuf);

	for (i= 0; i < NumNetILFiles; i++)
	{
		BytecodeHandle = nvmAssembleNetILFromFile(FileNamesArray[i], Errbuf);

		if (BytecodeHandle == NULL)
		{
			printf("\nError: %s\n\n", Errbuf);
			return (nvmFAILURE);
		}

		// Create the PE for the bytecode
		CurrentNetPE = nvmCreatePE(NetVMHandler, BytecodeHandle, Errbuf);

		if (PrevNetPE == NULL)
		{
			nvmConnectSocket2PE(NetVMHandler, SocketIn, CurrentNetPE, 0, Errbuf);
		}
		else
		{
			nvmConnectPE2PE(NetVMHandler, PrevNetPE, 1, CurrentNetPE, 0, Errbuf);
		}
		
		PrevNetPE = CurrentNetPE;
	}

	nvmConnectSocket2PE(NetVMHandler, SocketOut, CurrentNetPE, 1, Errbuf);

	// Create the NetVM Runtime environment that will compile the application
	NetVMRuntimeHandler= nvmCreateRTEnv(NetVMHandler, nvmRUNTIME_COMPILEONLY, Errbuf);

	JitFlags = nvmDO_ASSEMBLY;
	if (NetVMOptions->DoInline)
		JitFlags |= nvmDO_INLINE;
	if (NetVMOptions->DoBcheck)
		JitFlags |= nvmDO_BCHECK;

	nvmCompileApplication(NetVMHandler, NetVMRuntimeHandler, NetVMOptions->BackendID, JitFlags, NetVMOptions->OptLevel, NetVMOptions->OutputFilePrefix, Errbuf);

	nvmDestroyRTEnv(NetVMRuntimeHandler);

	return (nvmSUCCESS);
}



void Usage()
{
char string[]= \
	"\nUsage:\n"	\
	"  netvmcompiler -b backendID [options]\n\n"	\
	"Example:\n"	\
	"  netvmcompiler -b 0 sample.netil\n\n"			\
	"Options:\n"	\
	" -o opt_level: specifies an optimization level (0= disable all, 3= enable all)\n"	\
	" -p prefix: specifies the name of the output files that will contain the\n"		\
	"     native code generated by the NetVM compiler\n"								\
	" -b backend: specifies the backend to use\n"										\
	" -i: the produced code will use the 'inline' flag\n"								\
	" -backends: prints the list of the available backends\n"							\
	" -h: prints this help message.\n\n"												\
	"Description\n"																		\
	"===============================================================================\n"	\
	"This program loads one or more NetIL assembly files and compiles them into\n"		\
	"native code, according to the selected backend.\n"									\
	"This program does not execute the resulting code; it loads each NetIL file\n"		\
	"(specified on the command line), it creates a new NetPE for each file and it\n"	\
	"generates the corresponding native code.\n"										\
	"The resulting NetPE are connected together (the output of the first will become\n"	\
	"the input of the second), creating a 'pipeline' of NetPEs.\n"						\
	"Finally, the assembly files associated to each NetPE are dumped on disk.\n\n";

	printf("%s", string);

	exit (1);
}

void PrintFlags(char *FlagString, uint32_t Flags)
{
int CurrentPos;

if( (Flags & nvmDO_ASSEMBLY) != 0 )
		CurrentPos = snprintf(FlagString, 100, "ASSEMBLY ");
	if( (Flags & nvmDO_NATIVE) != 0)
		CurrentPos += snprintf(&FlagString[CurrentPos], 100, "NATIVE ");
	if( (Flags & nvmDO_INLINE) != 0)
		CurrentPos += snprintf(&FlagString[CurrentPos], 100, "INLINE ");
	if( (Flags & nvmDO_INIT) != 0)
		CurrentPos += snprintf(&FlagString[CurrentPos], 100, "INIT ");
	if( (Flags & nvmDO_BCHECK) != 0)
		CurrentPos += snprintf(&FlagString[CurrentPos], 100, "BOUNDCHECKS");
}


void ListBackends()
{
uint32_t NBackends, i;
char flags_str[40];

	nvmBackendDescriptor *BackendList= nvmGetBackendList(&NBackends);
	
	fprintf(stdout, "Avalable backends:\n");
	fprintf(stdout, "N\t\tName\t\tMax Opt\t\tCan Do\n");
	for (i = 0; i < NBackends; i++)
	{
		PrintFlags(flags_str, BackendList[i].Flags);
		fprintf(stdout, "%d\t\t%s\t\t%d\t\t%s\n", i, BackendList[i].Name, BackendList[i].MaxOptLevel, flags_str);
	}
}


int PrintSelectedBackend(unsigned int BackendID)
{
uint32_t NBackends;

	nvmBackendDescriptor *BackendList= nvmGetBackendList(&NBackends);

	if (NBackends == 0)
	{
		printf("\nThe NetBee library does not seem to have any native backend active.\nDid you turned the native backends on in the NetBee makefile before\n  compiling the library?\n\n");
		return nvmFAILURE;
	}

	if (BackendID < NBackends)
	{
		printf("\nGenerating native code for the '%s' backend.\n\n", BackendList[BackendID].Name);
		return nvmSUCCESS;
	}
	else
	{
		printf("\nSelected a backend that is out of range (0-%d).\n\n", NBackends);
		return nvmFAILURE;
	}

}


int main (int argc, char **argv)
{
struct options NetVMOptions;
int CurrentItem= 1;
char* DefaultNetILFile;

	NetVMOptions.OutputFilePrefix= DEFAULT_NATIVEASMFILENAME;
	NetVMOptions.BackendID= 0;	// It uses the first available backend
	NetVMOptions.OptLevel= 3;
	NetVMOptions.DoInline= 0;
	NetVMOptions.DoBcheck= 0;
	DefaultNetILFile= DEFAULT_NETILFILENAME;

	while (CurrentItem < argc)
	{
		if (strcmp(argv[CurrentItem], "-h") == 0)
		{
			Usage();
			return -1;
		}

		if (strcmp(argv[CurrentItem], "-o") == 0)
		{
			NetVMOptions.OptLevel= atoi(argv[CurrentItem+1]);
			CurrentItem+= 2;
			continue;
		}

		if (strcmp(argv[CurrentItem], "-p") == 0)
		{
			NetVMOptions.OutputFilePrefix= argv[CurrentItem+1];
			CurrentItem+= 2;
			continue;
		}

		if (strcmp(argv[CurrentItem], "-b") == 0)
		{
			NetVMOptions.BackendID= atoi(argv[CurrentItem+1]);
			CurrentItem+= 2;
			continue;
		}

		if (strcmp(argv[CurrentItem], "-bcheck") == 0)
		{
			NetVMOptions.DoBcheck= 1;
			CurrentItem+= 1;
			continue;
		}

		if (strcmp(argv[CurrentItem], "-backends") == 0)
		{
			ListBackends();
			return -1;
		}

		if (strcmp(argv[CurrentItem], "-i") == 0)
		{
			NetVMOptions.DoInline= 1;
			CurrentItem+= 1;
			continue;
		}

		if (strcmp(argv[CurrentItem], "-") == 0)
		{
			printf("\nCommand line option not recognized\n\n");
			Usage();
			exit(-1);
		}
		else
			// We reached this point: it should be the NetIL file. So, let's abort reading params
			break;

	}

	// Let's check if the user selected a set of NetIL files. If not, we will provide one by default
	if (argc == CurrentItem)
		printf("\nCompiling NetIL file '%s'\n\n", DefaultNetILFile);
	else
	{
	int i;

		printf("\nCompiling NetIL files ");
		for (i= CurrentItem; i < argc; i++)
			printf("'%s', ", argv[i]);

		printf("\n\n");
	}

	if (PrintSelectedBackend(NetVMOptions.BackendID) == nvmFAILURE)
		exit(-1);

	if (argc == CurrentItem)
	{
		if (NetVMSetup (&DefaultNetILFile, 1, &NetVMOptions) != nvmSUCCESS)
			exit(-1);
	}
	else
	{
		if (NetVMSetup (&argv[CurrentItem], argc - CurrentItem, &NetVMOptions) != nvmSUCCESS)
			exit(-1);
	}

	printf("\nThe native code of each NetPE have been saved on disk; please\n");
	printf("check files '%s*.s' for more details.\n\n", NetVMOptions.OutputFilePrefix);

	exit(0);
}

